<?php
/* SVN FILE: $Id: numbers.php 174 2009-08-25 22:40:13Z supersmashbrothers $ */
/**
* ibMarket 2
* IP.Board 3.0
* ibMarket numbers handler
* The idea enable greater numbers or higher precision for floats
*
* @author $Author: supersmashbrothers $
* @copyright $Copyright$
* @version $Revision: 174 $
* @lastrevision $Date: 2009-08-25 22:40:13 +0000 (Tue, 25 Aug 2009) $
* @modifiedby $LastChangedBy: supersmashbrothers $
* @lastmodified $LastChangedDate: 2009-08-25 22:40:13 +0000 (Tue, 25 Aug 2009) $
* @filesource $URL: http://ipb-ibmarket.googlecode.com/svn/ibmarket2/trunk/admin/applications_addon/other/ibmarket/sources/numbers.php $
*/

final class numbers
{
	/**
	 * The precision when using bcmath, default is 20. The bigger the number, the long it takes to calculate
	 *
	 * @access	public
	 * @var		int
	 */
	const precision = 20;
	
	/**
	 * Sets when the class is initiated
	 *
	 * @access	private
	 * @var		boolean
	 */
	private static $_is_init = false;
	
	/**
	 * Tells if bcmath is enabled on the server
	 *
	 * @access	private
	 * @var		boolean
	 */
	private static $_is_bcmath_enabled = false;
	
	/**
	 * Stores the calucated number of previous factorials
	 *
	 * @access	private
	 * @var		array
	 */
	private static $cache_factorial = array(0 => 1);
	
	/**
	 * Turn off constructor
	 *
	 * @access	private
	 * @return	void
	 */
	private function __construct() {}
	
	/**
	 * Sets up this class. To be called first and only once.
	 *
	 * @access	private
	 * @return	void
	 */
	public static function __init()
	{
		if(self::$_is_init)
		{
			return;
		}
		
		self::$_is_bcmath_enabled = (function_exists('bcadd') &&
									 function_exists('bccomp') &&
									 function_exists('bcdiv') &&
									 function_exists('bcmul') &&
									 function_exists('bcpow') &&
									 function_exists('bcmod') &&
									 function_exists('bcsub'));
		
		self::$_is_init = true;
	}
	
	/**
	 * Clean numbers the number when using the number as parameter
	 *
	 * @access	public
	 * @param	int|string	$number	Number to be cleaned
	 * @return	int|string			A cleaner number
	 */
	private static function clean_param_number($number)
	{
		if(!is_numeric($number) || is_object($number) || is_array($number))
		{
			throw new Exception("One of the arguments is not a number");
		}
		
		$_number = intval($number);
		if($_number == $number)
		{
			return $_number;
		}
		
		return $number;
	}
	
	/**
	 * Clean numbers the number when returning from a function
	 *
	 * @access	public
	 * @param	int|string	$number	Number to be cleaned
	 * @return	int|string			A cleaner number
	 */
	private static function clean_result_number($number)
	{
		if(is_string($number))
		{
			if(strstr($number,".") !== FALSE)
			{
				$number = rtrim($number,"0");
				$number = rtrim($number,".");
			}
			return self::clean_param_number($number);
		}
		elseif(is_float($number))
		{
			return self::clean_param_number($number);
		}
		else
		{
			return intval($number);
		}
	}
	
	/**
	 * Add numbers together
	 *
	 * @access	public
	 * @param	mixed	...	The addends
	 * @return	int|string	The sum of all the numbers
	 */
	public static function add()
	{
		$sum = 0;
		foreach(func_get_args() as $addend)
		{
			$addend = self::clean_param_number($addend);
			if(is_int($sum) || !self::$_is_bcmath_enabled)
			{
				$temp = $sum + $addend;
				if(!is_int($temp) && self::$_is_bcmath_enabled)
				{
					$temp = bcadd($sum, $addend, self::precision);
				}
				$sum = $temp;
			}
			else
			{
				$sum = bcadd($sum, $addend, self::precision);
			}
		}
		
		return self::clean_result_number($sum);
	}
	
	/**
	 * Subtract the two numbers
	 *
	 * @access	public
	 * @param	mixed	$minuend	The minuend
	 * @param	mixed	$subtrahend	The subtrahend
	 * @return	int|string			The difference, $minuend - $subtrahend
	 */
	public static function subtract($minuend, $subtrahend)
	{
		$minuend = self::clean_param_number($minuend);
		$subtrahend = self::clean_param_number($subtrahend);
		
		$difference = $minuend - $subtrahend;
		if(!is_int($difference) && self::$_is_bcmath_enabled)
		{
			$difference = bcsub($minuend, $subtrahend, self::precision);
		}
		
		return self::clean_result_number($difference);
	}
	
	/**
	 * Multiply numbers together
	 *
	 * @access	public
	 * @param	mixed	...	The multiplicands
	 * @return	int|string	The product of all the numbers
	 */
	public static function multiply()
	{
		$product = 1;
		foreach(func_get_args() as $multiplicand)
		{
			$multiplicand = self::clean_param_number($multiplicand);
			if(is_int($product) || !self::$_is_bcmath_enabled)
			{
				$temp = $product * $multiplicand;
				if(!is_int($temp) && self::$_is_bcmath_enabled)
				{
					$temp = bcmul($product, $multiplicand, self::precision);
				}
				$product = $temp;
			}
			else
			{
				$product = bcmul($product, $multiplicand, self::precision);
			}
		}
		
		return self::clean_result_number($product);
	}
	
	/**
	 * Divide the two numbers
	 *
	 * @access	public
	 * @param	mixed	$dividend	The minuend
	 * @param	mixed	$divisor	The subtrahend
	 * @return	int|string			The quotient, $dividend / $divisor
	 */
	public static function divide($dividend, $divisor)
	{
		$dividend = self::clean_param_number($dividend);
		$divisor = self::clean_param_number($divisor);
		
		$quotient = $dividend / $divisor;
		if(!is_int($quotient) && self::$_is_bcmath_enabled)
		{
			$quotient = bcdiv($dividend, $divisor, self::precision);
		}
		
		return self::clean_result_number($quotient);
	}
	
	/**
	 * Divide the two numbers
	 *
	 * @access	public
	 * @param	mixed	$dividend	The minuend
	 * @param	mixed	$divisor	The subtrahend
	 * @return	int					The quotient as an integer, $dividend / $divisor
	 */
	public static function modulus($dividend, $divisor)
	{
		$dividend = self::clean_param_number($dividend);
		$divisor = self::clean_param_number($divisor);
		
		$remainder = $dividend % $divisor;
		if(!is_int($remainder) && self::$_is_bcmath_enabled)
		{
			$remainder = bcmod($dividend, $divisor);
		}
		
		return self::clean_result_number($remainder);
	}
	
	/**
	 * Find the exponentiation of the base from the exponent
	 *
	 * @access	public
	 * @param	mixed	$base		The base
	 * @param	mixed	$exponent	The exponent
	 * @return	int|string			The result, $base ^ $exponent
	 */
	public static function power($base, $exponent)
	{
		$base = self::clean_param_number($base);
		$exponent = self::clean_param_number($exponent);
		
		$result = pow($base, $exponent);
		if(!is_int($result) && self::$_is_bcmath_enabled)
		{
			$result = bcpow($base, $exponent, self::precision);
		}
		
		return self::clean_result_number($result);
	}
	
	/**
	 * Find square root of a number
	 *
	 * @access	public
	 * @param	mixed	$number	The number to be square-rooted
	 * @return	int|string		The square root of the number
	 */
	public static function sqrt($number)
	{
		$number = self::clean_param_number($number);
		if(self::$_is_bcmath_enabled)
		{
			$sqrt = bcsqrt($number,$this->precision);
		}
		else
		{
			$sqrt = sqrt($number);
		}
		
		return self::clean_result_number($sqrt);
	}
	
	/**
	 * Compare two numbers
	 *
	 * @access	public
	 * @param	mixed	$number1	A number
	 * @param	mixed	[$number2]	A number, defaults to 0
	 * @return	int		The square root of the number
	 */
	public static function compare($number1, $number2 = 0)
	{
		$number1 = self::clean_param_number($number1);
		$number2 = self::clean_param_number($number2);
		
		if((is_int($number1) && is_int($number2)) || !self::$_is_bcmath_enabled)
		{
			if($number1 > $number2)
			{
				$compared = 1;
			}
			elseif($number1 < $number2)
			{
				$compared = -1;
			}
			else
			{
				$compared = 0;
			}
		}
		else
		{
			$compared = bccomp($number1, $number2, self::precision);
		}
		
		return $compared;
	}
	
	/**
	 * Find the smallest number
	 *
	 * @access	public
	 * @param	mixed	...	The numbers
	 * @return	mixed		The smallest of all the numbers
	 */
	public static function min()
	{
		$min = NULL;
		
		foreach(func_get_args() as $number)
		{
			if(is_null($min) || self::compare($min,$number) > 0)
			{
				$min = $number;
			}
		}
		
		return $min;
	}
	
	/**
	 * Find the largest number
	 *
	 * @access	public
	 * @param	mixed	...	The numbers
	 * @return	mixed		The largest of all the numbers
	 */
	public static function max()
	{
		$max = NULL;
		
		foreach(func_get_args() as $number)
		{
			if(is_null($max) || self::compare($max,$number) < 0)
			{
				$max = $number;
			}
		}
		
		return $max;
	}
	
	/**
	 * Floor the number
	 *
	 * @access	public
	 * @param	mixed	$number			The number to be rounded
	 * @param	int		$decimalPlaces	The number of decimal places to roud to (negative numbers accepted)
	 * @return	mixed					The rounded number
	 */
	public static function floor($number)
	{
		$number = self::clean_param_number($number);
		if(self::compare($number) < 0)
		{
			return self::multiply(self::ceil(self::multiply($number,-1)),-1);
		}
		if(self::$_is_bcmath_enabled)
		{
			$floor = bcmul($number, 1, 0);
		}
		else
		{
			$floor = floor($number);
		}
		return self::clean_result_number($floor);
	}
	
	/**
	 * Ceil the number
	 *
	 * @access	public
	 * @param	mixed	$number			The number to be rounded
	 * @param	int		$decimalPlaces	The number of decimal places to roud to (negative numbers accepted)
	 * @return	mixed					The rounded number
	 */
	public static function ceil($number)
	{
		$number = self::clean_param_number($number);
		if(self::compare($number) < 0)
		{
			return self::multiply(self::floor(self::multiply($number,-1)),-1);
		}
		if(self::$_is_bcmath_enabled)
		{
			$floor = self::floor($number);
			$ceil = bcadd($floor, ceil(bcsub($number, $floor, 2)), 0);
		}
		else
		{
			$ceil = ceil($number);
		}
		return self::clean_result_number($ceil);
	}
	
	/**
	 * Round the number
	 *
	 * @access	public
	 * @param	mixed	$number				The number to be rounded
	 * @param	int		[$decimalPlaces]	The number of decimal places to roud to (negative numbers accepted)
	 * @return	mixed						The rounded number
	 */
	public static function round($number, $decimalPlaces = 0)
	{
		$negative = self::compare($number) < 0;
		if($negative)
		{
			$number = self::multiply($number, -1);
		}
		$adjust = self::power(10,$decimalPlaces);
		$number = self::multiply($number,$adjust);
		$number = self::_round($number);
		$number = self::divide($number,$adjust);
		if($negative)
		{
			$number = self::multiply($number, -1);
		}
		return $number;
	}
	
	/**
	 * Round the number method helper
	 *
	 * @access	private
	 * @param	mixed	$number			The number to be rounded
	 * @return	mixed					The rounded number
	 */
	private static function _round($number)
	{
		$_number = $number = self::clean_param_number($number);
		if(self::$_is_bcmath_enabled)
		{
			$floor = self::floor($number);
			$rounded = bcadd($floor, round(bcsub($number, $floor, 2)), 0);
		}
		else
		{
			$rounded = round($number);
		}
		return self::clean_result_number($rounded);
	}
	
	/**
	 * Calculate factorial of a number
	 *
	 * @access	public
	 * @param	int	$number	The number to find the factorial
	 * @return	int|string	Returns ($number)!
	 */
	public static function factorial($number = 0)
	{
		$number = intval($number);
		if($number < 0)
		{
			throw new Exception("Cannot find factorial of a negative number");
		}
		if(isset(self::$cache_factorial[$number]))
		{
			return self::$cache_factorial[$number];
		}
		
		$f = end(self::$cache_factorial);
		$n = key(self::$cache_factorial);
		for($i = $n + 1; $i <= $number; $i++)
		{
			self::$cache_factorial[$i] = $f = self::multiply($f,$i);
		}
		
		return self::$cache_factorial[$number];
	}
	
	/**
	 * Calculate $n permutation $r
	 *
	 * @access	public
	 * @param	int	$n	The n in nPr
	 * @param	int	$r	The n in nPr
	 * @return	int|string	Returns $n nPr $r
	 */
	public static function nPr($n, $r)
	{
		return self::divide(self::factorial($n),self::factorial($n-$r));
	}
	
	/**
	 * Calculate $n choose $r
	 *
	 * @access	public
	 * @param	int	$n	The n in nCr
	 * @param	int	$r	The n in nCr
	 * @return	int|string	Returns $n nCr $r
	 */
	public static function nCr($n, $r)
	{
		return self::divide(self::factorial($n),self::multiply(self::factorial($r),self::factorial($n-$r)));
	}
	
	/**
	 * Chooses a random number
	 *
	 * @access	public
	 * @param	intval	[$min]		The minimum number to pick, inclusive
	 * @param	int		[$max]		The maximun number to pick, inclusive
	 * @param	boolean	[$decimal]	To pick as a decimal number
	 * @return	mixed				Random number
	 */
	public static function rand($min = 0,$max = 2147483647,$decimal = false)
	{
		list($usec, $sec) = explode(" ", microtime());
		mt_srand(intval($usec*1000+$usec*1000000000+$sec));
		$rand_dec = self::divide(mt_rand(0,2147483647),2147483647);
		
		if($decimal)
		{
			$diff = self::subtract($max,$min);
			$rand = self::add($min,self::multiply($rand_dec,$diff));
			return $rand;
		}
		else
		{
			$diff = self::add(self::subtract($max,$min),1);
			$rand = self::add(self::subtract($min,.5),self::multiply($rand_dec,$diff));
			return self::round($rand);
		}
	}
	
	/**
	 * Make anything into an int
	 *
	 * @access	public
	 * @param	mixed	[$value]
	 * @return	int|string
	 */
	public static function parseInt($value)
	{
		// If it is already an int
		// Don't bother do other checking
		if(is_int($value))
		{
			return $value;
		}
		$_value = intval($value);
		if(self::$_is_bcmath_enabled)
		{
			$_v = bcadd($value, 0, 0);
			if($_value != $_v)
			{
				$_value = $_v;
			}
		}
		return $_value;
	}
	
	/**
	 * Make anything into a decimal number
	 *
	 * @access	public
	 * @param	mixed	[$value]
	 * @return	float|string
	 */
	public static function parseFloat($value)
	{
		if(self::$_is_bcmath_enabled)
		{
			return bcadd($value, 0, self::precision);
		}
		return floatval($value);
	}
}

numbers::__init();
?>